Skip to content

refactor(RangePicker): centralize interaction flow#994

Draft
zombieJ wants to merge 36 commits into
masterfrom
fix/range-picker-confirm-flow
Draft

refactor(RangePicker): centralize interaction flow#994
zombieJ wants to merge 36 commits into
masterfrom
fix/range-picker-confirm-flow

Conversation

@zombieJ

@zombieJ zombieJ commented Jul 20, 2026

Copy link
Copy Markdown
Member

Summary

  • add dedicated focus and range value change hooks
  • route input, keyboard, panel, field switch, confirm, and blur interactions through one event-driven flow
  • distinguish intermediate and final panel selections
  • reset unconfirmed calendar values without coupling popup close to value submission
  • keep PickerTrigger.onClose responsible only for popup visibility

Motivation

RangePicker currently spreads part-submit and reset behavior across event handlers and an effect. This refactor prepares a single event-driven decision point for confirm and non-confirm flows, following the behavior discussed around #966.

Impact

This changes RangePicker's internal interaction handling only. The existing useRangeValue hook remains responsible for value validation and final submission.

Validation

  • ut lint:tsc
  • ut lint — 0 errors
  • ✅ full test suite — 15 suites, 453 passed, 2 skipped

Interaction flow

canSwitch = 当前值非空 || allowEmpty[currentIndex]

allFieldsTriggered = triggeredFields.length >= fieldCount

  • ✏️ modify
  • ➡️ switchNext
  • ⬅️ switchPrevious
  • finish
  • abort
  • ↩️ resetCurrent
  • 🔄 resetCurrentAndSwitchNext
  • 🗑️ resetAll
flowchart TB
  START["① 入口与 field-switch<br/>triggerChange(index, source, value?)"] --> ESC{"🔎 source === esc?"}

  ESC -- "是" --> ESC_RESET["🗑️ action = resetAll"]
  ESC -- "否" --> IDLE{"🔎 currentIndex === null?"}

  IDLE -- "是,source = blur" --> IDLE_RESET["🗑️ action = resetAll"]
  IDLE -- "是,其他事件" --> INIT["📌 初始化 currentIndex = index<br/>记录 triggered field"]
  IDLE -- "否" --> ROUTE{"🔎 source === field-switch?"}
  INIT --> ROUTE

  ROUTE -- "否" --> CURRENT_ENTRY["进入 ② 当前 field 事件"]
  ROUTE -- "是" --> FS_SAME{"🔎 index === currentIndex?"}

  FS_SAME -- "是" --> FS_ABORT_SAME["⛔ action = abort"]
  FS_SAME -- "否" --> FS_PREVIOUS{"🔎 是否为已访问的上一个 field?"}

  FS_PREVIOUS -- "是" --> FS_PREVIOUS_LOCK{"🔎 needConfirm<br/>且当前值非空、未确认<br/>且不允许空?"}
  FS_PREVIOUS_LOCK -- "是" --> FS_ABORT_PREVIOUS["⛔ action = abort"]
  FS_PREVIOUS_LOCK -- "否" --> FS_SWITCH_PREVIOUS["⬅️ action = switchPrevious"]

  FS_PREVIOUS -- "否" --> FS_NEXT{"🔎 index === nextIndex?"}
  FS_NEXT -- "否" --> FS_ABORT_INVALID["⛔ action = abort"]
  FS_NEXT -- "是" --> FS_CONFIRM{"🔎 needConfirm?"}

  FS_CONFIRM -- "否" --> FS_CAN_SWITCH{"🔎 canSwitch?"}
  FS_CAN_SWITCH -- "是" --> FS_SWITCH_NEXT["➡️ action = switchNext"]
  FS_CAN_SWITCH -- "否" --> FS_RESET_CURRENT["↩️ action = resetCurrent"]

  FS_CONFIRM -- "是" --> FS_CONFIRMED{"🔎 当前 field 已确认?"}
  FS_CONFIRMED -- "是" --> FS_SWITCH_CONFIRMED["➡️ action = switchNext"]
  FS_CONFIRMED -- "否" --> FS_ALLOW_EMPTY{"🔎 allowEmpty[currentIndex]?"}
  FS_ALLOW_EMPTY -- "是" --> FS_RESET_SWITCH["🔄 action = resetCurrentAndSwitchNext"]
  FS_ALLOW_EMPTY -- "否" --> FS_ABORT_LOCKED["⛔ action = abort"]
Loading
flowchart TB
  START["② 当前 field 事件<br/>非 field-switch"] --> CURRENT_INDEX{"🔎 index === currentIndex?"}

  CURRENT_INDEX -- "否" --> CURRENT_ABORT["⛔ action = abort"]
  CURRENT_INDEX -- "是" --> SOURCE{"🔎 source 类型"}

  SOURCE -- "input / panel-intermediate" --> MODIFY["✏️ action = modify"]

  SOURCE -- "panel-final" --> PANEL_CONFIRM{"🔎 needConfirm?"}
  PANEL_CONFIRM -- "是" --> PANEL_MODIFY["✏️ action = modify"]
  PANEL_CONFIRM -- "否" --> PANEL_SWITCH["➡️ action = switchNext"]

  SOURCE -- "keyboard-submit / confirm" --> SUBMIT_VALID{"🔎 canSwitch?"}
  SUBMIT_VALID -- "是" --> SUBMIT_SWITCH["➡️ action = switchNext"]
  SUBMIT_VALID -- "否" --> SUBMIT_ABORT["⛔ action = abort"]

  SOURCE -- "blur" --> BLUR_ENTRY["进入 ③ blur 分支"]
Loading
flowchart TB
  START["③ blur 分支"] --> BLUR_MODIFIED{"🔎 本轮是否修改过任意 field?"}

  BLUR_MODIFIED -- "否" --> BLUR_FINISH["✅ action = finish"]
  BLUR_MODIFIED -- "是" --> BLUR_CONFIRM{"🔎 needConfirm?"}

  BLUR_CONFIRM -- "否" --> BLUR_CAN_SWITCH{"🔎 canSwitch?"}
  BLUR_CAN_SWITCH -- "是" --> BLUR_SWITCH["➡️ action = switchNext"]
  BLUR_CAN_SWITCH -- "否" --> BLUR_RESET_INVALID["🗑️ action = resetAll"]

  BLUR_CONFIRM -- "是" --> BLUR_READY{"🔎 allFieldsTriggered<br/>且 canSwitch?"}
  BLUR_READY -- "否" --> BLUR_RESET_ALL["🗑️ action = resetAll"]
  BLUR_READY -- "是" --> BLUR_CURRENT_MODIFIED{"🔎 当前 field 是否修改过?"}

  BLUR_CURRENT_MODIFIED -- "否" --> BLUR_FINAL_SWITCH["➡️ action = switchNext"]
  BLUR_CURRENT_MODIFIED -- "是" --> BLUR_ALLOW_EMPTY{"🔎 allowEmpty[currentIndex]?"}
  BLUR_ALLOW_EMPTY -- "是" --> BLUR_RESET_SWITCH["🔄 action = resetCurrentAndSwitchNext"]
  BLUR_ALLOW_EMPTY -- "否" --> BLUR_RESET_UNCONFIRMED["🗑️ action = resetAll"]
Loading

@vercel

vercel Bot commented Jul 20, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
picker Ready Ready Preview, Comment Jul 22, 2026 4:06pm

@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4d646035-65a8-4f6a-a08a-387cf7d5fd33

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/range-picker-confirm-flow

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown

React Doctor found no new issues. 🎉

Reviewed by React Doctor for commit 5c2f063.

@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown

❌ Deploy failed

PR preview ❌ Failed ❌ Failed
🔗 Preview https://react-component-picker-preview-pr-994.surge.sh (may be unavailable)
📝 Commit5c2f063
🪵 LogsView logs
📋 Build log (last lines)
npm error
npm error Could not resolve dependency:
npm error peer eslint@"^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" from eslint-plugin-react@7.37.5
npm error node_modules/eslint-plugin-react
npm error   dev eslint-plugin-react@"^7.37.5" from the root project
npm error   eslint-plugin-react@"^7.32.2" from @umijs/fabric@4.0.1
npm error   node_modules/@umijs/fabric
npm error     @umijs/fabric@"^4.0.0" from rc-test@7.1.3
npm error     node_modules/rc-test
npm error       dev rc-test@"^7.1.3" from the root project
npm error
npm error Conflicting peer dependency: eslint@9.39.5
npm error node_modules/eslint
npm error   peer eslint@"^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" from eslint-plugin-react@7.37.5
npm error   node_modules/eslint-plugin-react
npm error     dev eslint-plugin-react@"^7.37.5" from the root project
npm error     eslint-plugin-react@"^7.32.2" from @umijs/fabric@4.0.1
npm error     node_modules/@umijs/fabric
npm error       @umijs/fabric@"^4.0.0" from rc-test@7.1.3
npm error       node_modules/rc-test
npm error         dev rc-test@"^7.1.3" from the root project
npm error
npm error Fix the upstream dependency conflict, or retry
npm error this command with --force or --legacy-peer-deps
npm error to accept an incorrect (and potentially broken) dependency resolution.
npm error
npm error
npm error For a full report see:
npm error /home/runner/.npm/_logs/2026-07-22T16_07_02_173Z-eresolve-report.txt
npm error A complete log of this run can be found in: /home/runner/.npm/_logs/2026-07-22T16_07_02_173Z-debug-0.log

🤖 Powered by surge-preview

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors focus and value change handling in the RangePicker component by introducing the useFocusControl and useRangeValueChange hooks. These hooks streamline the coordination of calendar value updates, partial/final submissions, and focus/blur states across multiple fields. The feedback suggests a critical fix in useRangeValueChange to allow smooth field switching when needConfirm is false and the previous field is empty, as well as adding optional chaining to selectorRef.current accesses in RangePicker.tsx to prevent potential runtime errors.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/PickerInput/hooks/useRangeValueChange.ts Outdated
Comment thread src/PickerInput/RangePicker.tsx Outdated
Comment thread src/PickerInput/RangePicker.tsx Outdated
Comment thread src/PickerInput/RangePicker.tsx Outdated

// ============================= Utils =============================
/** Check whether the target is the container itself or inside it. / 判断目标是否为容器自身或其子元素。 */
function containsElement(container: Element | null, target: EventTarget | null) {

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这个函数直接在 isTargetInContainers 里内联掉,不要额外写个函数

@codecov

codecov Bot commented Jul 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.20548% with 14 lines in your changes missing coverage. Please review.
✅ Project coverage is 98.44%. Comparing base (fc53277) to head (87c4c07).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
src/PickerInput/hooks/useRangeValueChange.ts 93.87% 9 Missing ⚠️
src/PickerInput/SinglePicker.tsx 92.30% 3 Missing ⚠️
src/PickerInput/RangePicker.tsx 95.00% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #994      +/-   ##
==========================================
- Coverage   98.81%   98.44%   -0.37%     
==========================================
  Files          66       68       +2     
  Lines        2698     2830     +132     
  Branches      724      803      +79     
==========================================
+ Hits         2666     2786     +120     
- Misses         29       41      +12     
  Partials        3        3              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment on lines +381 to +404
case 'switchPrevious': {
if (!needConfirm) {
const currentValue = getCalendarValue()[actionIndex];
const currentEmpty = currentValue === null || currentValue === undefined;

if (!currentEmpty || allowEmpty[actionIndex]) {
// Going back may part-submit the current field, but must never
// finish the whole round before the previous field is edited.
// 返回上一个 field 时可以局部提交当前 field,但不能在用户修改
// 上一个 field 前结束整轮提交。
flushSubmit(actionIndex, false);
} else {
resetValue(actionIndex);
}
}

const previousPosition = triggeredFieldsRef.current.findIndex(
(field) => field.index === index,
);
triggeredFieldsRef.current = triggeredFieldsRef.current.slice(0, previousPosition + 1);
recordTriggeredField(index, false);
setCurrentIndex(index);
break;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needConfirm 场景切回 previous field 时,这里不会 reset 当前未确认值,但后面会移除对应的 tracking。这样 CalendarValue 仍然保留,状态机却不再记录,后续 blur 是否可能直接走 finish,导致未确认值残留?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants